/************************************************************************
Copyright 2003, 2004, 2005, 2006, 2007, 2008 Grid Technology Research Center,
National Institute of Advanced Industrial Science and Technology.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
************************************************************************/

package jp.aist.gtrc.plus.scheduler.specific.sge.status;

import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;

import jp.aist.gtrc.plus.reserve.ReserveId;
import jp.aist.gtrc.plus.scheduler.job.JobStateType;
import jp.aist.gtrc.plus.scheduler.node.NodeAllocateSet;
import jp.aist.gtrc.plus.scheduler.node.NodeResource;
import jp.aist.gtrc.plus.scheduler.specific.sge.jaxb.common.*;
import jp.aist.gtrc.plus.scheduler.specific.sge.sched.SGESelfExecNodeAllocator;
import jp.aist.gtrc.plus.scheduler.status.JobID;
import jp.aist.gtrc.plus.scheduler.status.JobStatus;
import jp.aist.gtrc.plus.scheduler.status.JobStatusBase;
import jp.aist.gtrc.plus.scheduler.status.NodeStatus;
import jp.aist.gtrc.plus.scheduler.util.CollectionUtil;
import jp.aist.gtrc.plus.scheduler.util.TextUtil;
import jp.aist.gtrc.plus.scheduler.util.TimeUtil;


public class SGEJobStatus extends JobStatusBase {

	private final JAXBSGEJob job;
	private final HashMap<Integer, SGETaskStatus> tasks;
	private final HashMap<String, ComplexStatus> hardReq;
	private final String tgtPe, tgtProj;
	private static final SGEStatusManager statusMgr = SGEStatusManager.getInstance();

	// local variables which may be referred many times.
	// make instance for better performance
	private ReserveId rsvid;
	private String[] tgtQs;
	private Integer reqProcnum;

	public static final String RSV_ID_KEY = "rsvid";
	private static final int RANGE_INFINITY = 9999999;

	public SGEJobStatus(JAXBSGEJob job) {

		assert(job != null);
		this.job = job;
		this.hardReq = new HashMap<String, ComplexStatus>();
		this.tasks = new LinkedHashMap<Integer, SGETaskStatus>();

		/*
		 * NOTE: These values may be referred so many times,
		 * so make non-null string here.
		 */
		this.tgtPe = TextUtil.toNonNull(job.getJBPe());
		this.tgtProj = TextUtil.toNonNull(job.getJBProject());

		makeTasksMap();
		makeHardRequests();

	}

	JAXBSGETask getTemplate() {

		JBJaTasks tmp = job.getJBJaTemplate();
		if (tmp == null)
			return null;
		List<JAXBSGETask> tmpList = tmp.getUlongSublist();
		if (CollectionUtil.isEmpty(tmpList))
			return null;
		return tmpList.get(0);

	}

	private void addTask(SGETaskStatus task) {

		tasks.put(task.getTaskNumber(), task);

	}

	private void addTasks(JBJaStructure idRange, int status) {

		if (idRange == null)
			return;
		RNElement ids = idRange.getTaskIdRange();
		if (ids == null)
			return;
		JAXBSGETask template = getTemplate();
		if (template == null)
			return;

		for (int tid = ids.getRNMin(); tid <= ids.getRNMax();
			tid += ids.getRNStep()) {
			if (tasks.containsKey(tid)) {
				continue;
			}
			SGETaskStatus newTask = SGETaskStatus.getDummyTask(this, tid, status);
			addTask(newTask);
		}

	}

	private void makeTasksMap() {

		// add running/exited tasks
		JBJaTasks aliveTasks = job.getJBJaTasks();
		if (aliveTasks != null) {
			for (JAXBSGETask t : aliveTasks.getUlongSublist()) {
				SGETaskStatus newTask = new SGETaskStatus(this, t);
				addTask(newTask);
			}
		}

		// add waiting to run (not held) tasks
		addTasks(job.getJBJaNHIds(), SGETaskStatus.TS_IDLE);

		// add holding tasks
		addTasks(job.getJBJaUHIds(), SGETaskStatus.TS_HELD);
		addTasks(job.getJBJaSHIds(), SGETaskStatus.TS_HELD);
		addTasks(job.getJBJaOHIds(), SGETaskStatus.TS_HELD);

	}

	private void makeHardRequests() {

		JBHardResourceList hrList = job.getJBHardResourceList();
		if (hrList != null) {
			for (CEElement element : hrList.getQstatLRequests()) {
				ComplexStatus c = new ComplexStatus(element);
				hardReq.put(c.getName(), c);
			}
		}

	}

	int getJobNumber() {

		return job.getJBJobNumber();

	}

	public String getOwner() {

		return job.getJBOwner();

	}

	public int getUid() {

		return job.getJBUid();

	}

	public int getPriority() {

		return job.getJBPriority();

	}

	public void setPriority(int newPrio) {

		job.setJBPriority(newPrio);

	}

	public Calendar getSubmitTime() {

		return TimeUtil.makeCalendarFromSec(job.getJBSubmissionTime());

	}

	public String getName() {

		return Integer.toString(getJobNumber());

	}

	public Collection<SGETaskStatus> getAllTasks() {

		return tasks.values();

	}

	public JobID getJobID() {
		// Call SGETaskStatus.getJobID(), not this.
		assert(false);
		return null;
	}

	public JobStateType getState() {
		// Call SGETaskStatus.getJobID(), not this.
		assert(false);
		return null;
	}

	public boolean isReadyToRun() {
		return true;
	}

	public Calendar getExecStartTime() {

		if (getState() == JobStateType.Running) {
			return TimeUtil.makeCalendarFromSec(job.getJBExecutionTime());
		} else {
			return null;
		}

	}

	public Calendar getRequestedExecStartTime() {

		/*
		 * NOTE: Job cannot run before exec_time.
		 * 'qsub -a MMDDhhmm' is used.
		 */
		return TimeUtil.makeCalendarFromSec(job.getJBExecutionTime());

	}

	public ReserveId getReserveId() {

		if (rsvid != null)
			return rsvid;

		JBContext c = job.getJBContext();
		if (c == null)
			return null;

		for (VAElement e : c.getContextList()) {
			if (e.getVAValue().equals(RSV_ID_KEY)) {
				rsvid = ReserveId.getInstance(e.getVAVariable());
				return rsvid;
			}
		}

		return null;
	}

	public int getRequestedCPUPerNode() {

		return 1;

	}

	private int checkRequestedProcess() {

		SGEParallelEnv pe = getTargetParallelEnv();
		if (pe == null) {
			return 1;
		} else {
			int allocRule = pe.getAllocationRule();
			if (pe.isProcessPerNodeSpecified()) {
				return allocRule;
			}
			switch (allocRule) {
			case SGEParallelEnv.ALLOC_RULE_FILL_UP:
				return 1;
			case SGEParallelEnv.ALLOC_RULE_PE_SLOTS:
				// start all processes on single node
				return getPEProcNum();
			case SGEParallelEnv.ALLOC_RULE_ROUND_ROBIN:
				return 1; // TODO
			default:
				assert(false);
				return 1;
			}
		}

	}

	public int getRequestedProcessPerNode() {

		if (reqProcnum == null)
			reqProcnum = checkRequestedProcess();
		return reqProcnum;

	}

	public int getRequestedNodeNum() {

		return getPEProcNum() / getRequestedProcessPerNode();

	}

	public Collection<NodeAllocateSet> getNodeRequests() {

		Collection<NodeAllocateSet> nodeReqs = new LinkedList<NodeAllocateSet>();
		int nodeNum = getRequestedNodeNum();
		int cpuNum = getRequestedCPUPerNode();
		int procNum = getRequestedProcessPerNode();
		ComplexStatus arch = hardReq.get("arch");
		if (arch == null) {
			NodeResource rsc = new NodeResource(cpuNum, procNum);
			nodeReqs.add(new NodeAllocateSet(nodeNum, rsc));
		} else {
			Collection<NodeResource> rscs = SGEArch.getMacthedNodeResources(
					arch.getStringVal(), cpuNum, procNum);
			nodeReqs.add(new NodeAllocateSet(nodeNum, rscs));
		}
		return nodeReqs;

	}

	public int getRequestedTime() {

		JBHardResourceList hardList = job.getJBHardResourceList();
		if (hardList == null)
			return WALLTIME_NOT_SPECIFIED;

		ComplexStatus req = hardReq.get("h_rt");
		if (req == null) {
			req = hardReq.get("h_cpu");
		}

		return (req != null)
				? (int)req.getDoubleVal() : WALLTIME_NOT_SPECIFIED;

	}

	public String getOwnersGroup() {

		return job.getJBGroup();

	}

	public int getGid() {

		return job.getJBGid();

	}

	public boolean isRunnableOn(NodeStatus node) {

		SGESelfExecNodeAllocator allocator = new SGESelfExecNodeAllocator();
		return allocator.isReadyToRun((JobStatus)this, node);

	}

	String[] getTargetQueueNames() {

		// returns {"foo.q"} if this job was submited by "qsub -q foo.q job.sh"
		// returns {} if -q options wasn't used.
		if (tgtQs != null)
			return tgtQs;

		JBHardQueueList hqList = job.getJBHardQueueList();
		if (hqList == null) {
			tgtQs = new String[]{};
		} else {
			Collection<QRElement> eleList = hqList.getDestinIdentList();
			tgtQs = new String[eleList.size()];
			int i = 0;
			for (QRElement e : eleList) {
				tgtQs[i] = e.getQRName();
				i++;
			}
		}
		return tgtQs;

	}

	String getTargetParallelEnvName() {

		return tgtPe;

	}

	SGEParallelEnv getTargetParallelEnv() {

		return statusMgr.getParallelEnv(getTargetParallelEnvName());

	}

	int[] getPEProcNumRange() {

		/*
		 * returns min&max of parallel process number
		 *
		 * ret					-pe option
		 * --------------------------------------------
		 *	{ 16, 16 }			-pe 16
		 *	{ 16, 32 }			-pe 16-32
		 *	{ 16, 9999999 }		-pe 16-
		 *	{  1, 16 }			-pe -16
		 *
		 *	9999999 is RANGE_INFINITY
		 */
		JBPeRange range = job.getJBPeRange();
		if (range != null) {
			RNElement e = range.getRanges();
			assert(e.getRNMax() <= RANGE_INFINITY);
			return new int[]{ e.getRNMin(), e.getRNMax() };
		} else {
			return null;
		}

	}

	int getPEProcNum() {

		int[] num = getPEProcNumRange();
		if (num == null)
			return 1;

		return (num[1] == RANGE_INFINITY) ? num[0] : num[1];

	}

	String getTargetProjectName() {

		// returns "proj_1" if this job was submitted by "qsub -P proj_1 job.sh"
		// returns "" if -P options wasn't used.
		return tgtProj;

	}

	public boolean isRestartable() {

		switch (job.getJBRestart()) {
		case 0:	// default value
			return false;
		case 1:	// 'qsub -r y' was used
			return true;
		case 2:	// 'qsub -r n' was used
			return false;
		default:
			assert(false);
			return false;
		}

	}

	public static JobStateType getState(String state) {

		if (state.contains("h")) {
			return JobStateType.Held;
		}
		if (state.contains("s") || state.equals("suspended")) {
			return JobStateType.Suspended;
		}

		if (state.equals("qw") || state.equals("pending")) {
			// NOTE: "pending" means "qw", "h", "hr"
			return JobStateType.Queued;
		}
		if (state.equals("r") || state.equals("running")) {
			return JobStateType.Running;
		}

		return JobStateType.Exiting;

	}

	public String toString() {

		StringBuffer sb = new StringBuffer(64);
		sb.append(super.toString());
		sb.append("\nqueue: ");
		for (String qName : getTargetQueueNames()) {
			sb.append(qName);
			sb.append(' ');
		}
		sb.append(" pe: ");
		sb.append(getTargetParallelEnvName());
		sb.append(" proj: ");
		sb.append(getTargetProjectName());
		sb.append("\nTasks in this job\n");
		for (SGETaskStatus t : tasks.values()) {
			sb.append(t.toString());
		}

		return sb.toString();

	}

}
